﻿//////////////////////////////////////////////////////////////////////////////////
/// Author:
/// Istvan.safar@gmail.com
/// 2009.12.10
//////////////////////////////////////////////////////////////////////////////////

#region using

using System;
using System.Collections.Generic;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using MsBuildAccess.Common;
using MsBuildAccess.Helper;
using Logger=MsBuildAccess.Helper.Logger;

#endregion

namespace MsBuildAccess.Import
{
    /// <summary>
    /// import access files from sourccontrol
    /// </summary>
    public class ImportAccessFromSourceSafe : Task
    {
        #region fields

        /// <summary>
        /// excluded file extension
        /// </summary>
        private List<string> excludedList;

        /// <summary>
        /// access files queue
        /// </summary>
        private Queue<string> files;

        /// <summary>
        /// logger instance
        /// </summary>
        private Logger logger;

        /// <summary>
        /// Vba project references
        /// </summary>
        private List<AccessReference> references;

        #endregion

        #region properties

        /// <summary>
        /// Import path name for sourcesafe access files
        /// </summary>
        [Required]        
        public string ImportPath { get; set; }

        /// <summary>
        /// OutputPath for access File
        /// </summary>
        [Required]
        public string OutputPath { get; set; }

        /// <summary>
        /// Access type
        /// </summary>
        [Required]
        public string AccesType { get; set; }

        /// <summary>
        /// Open this module and compile (will compile all module)
        /// </summary>
        public string CompileModuleName { get; set; }

        /// <summary>
        /// You must set CompileModulName
        /// References file name must in this format: name,guid,major,minor,kind\r\n
        /// </summary>
        public string ReferencesFileName { get; set; }

        /// <summary>
        /// excluded files:txt,acn,acb,scc
        /// </summary>
        public string ExcludedFileTypes { get; set; }

        /// <summary>
        /// Logtarget normal means msbuild logger console = console
        /// </summary>
        public string LogTarget { get; set; }

        #endregion
      
        #region constructor

        /// <summary>
        /// Initializes a new instance of the ImportAccessFromSourceSafe class.
        /// </summary>
        public ImportAccessFromSourceSafe()
        {
            this.LogTarget = "normal";
        }

        #endregion

        /// <summary>
        /// Execute task
        /// </summary>
        /// <returns>true when success</returns>
        public override bool Execute()
        {
            DateTime start = DateTime.Now;
            this.logger = new Logger(this.LogTarget, Log);

            if (!this.ValidatePoperties())
                return false;

            var accessType = (AccessType)Enum.Parse(typeof (AccessType), this.AccesType);
            bool ret = true;
            // msaccess does not support multitthread for importtext
            List<MsAccessFile> msAccessFiles = Factory.GetMsAccesList(this.OutputPath, accessType, 1, this.CompileModuleName, this.logger);

            try
            {
                #region logging

                this.logger.LogInternal("Build started\r\n", false);
                this.logger.LogInternal(string.Format("MsAccess opening:{0}", this.OutputPath), false);
                this.logger.LogInternal(string.Format("MsAccess opening:{0}", this.ImportPath), false);
                this.logger.LogInternal(string.Format("MsAccess importing{0}:", "*.a*"), false);

                #endregion

                this.FieldBuilder();

                this.logger.LogInternal(string.Format("MsAccess importing {0}:", this.files.Count), false);
                if (this.files != null && this.files.Count > 0)
                {
                    // todo multithread : //msaccess does not support multitthread for importtext
                    ret = this.FileImporter(msAccessFiles[0]);
                    this.logger.LogInternal(string.Format("ret afer import:{0}", ret), false);
                }
                else
                {
                    this.logger.LogInternal(string.Format("Nothing to do {0}", this.ImportPath), true);
                    ret = false;
                }

                if (ret)
                {
                    // first is enough                    
                    ret = this.CompileAndSaveMsAccessList(msAccessFiles[0]);
                    this.logger.LogInternal("MsAccess Build done", false);
                }
            }
            catch (Exception ex)
            {
                this.logger.LogInternal(string.Format("Error {0}", ex), true);
                ret = false;
            }
            finally
            {
                try
                {
                    ret = ret & CloseMsAccessList(msAccessFiles);
                    ret = ret & QuitMsAccessList(msAccessFiles);
                }
                catch (Exception)
                {
                    ret = false;
                }
            }
            
            this.logger.LogMsg(string.Format("Build time {0}",start.Subtract(DateTime.Now)));
            return ret;
        }

        /// <summary>
        /// validate properties
        /// </summary>
        /// <returns>returns true when success</returns>
        private bool ValidatePoperties()
        {
            bool ret = true;
            if (!Enum.IsDefined(typeof(AccessType), this.AccesType.ToLower()))
            {
                this.logger.LogErr(string.Format("AccessType is invalid only:mdb,accdb,adp is accepted:{0}",this.AccesType));
                ret = false;
            }

            if (!string.IsNullOrEmpty(this.ReferencesFileName) && string.IsNullOrEmpty(this.CompileModuleName))
            {
                this.logger.LogErr(string.Format("You must fill CompileModule propery when use ReferenceAccessFileName! FileName {0}, ModuleName{1} ",this.ReferencesFileName, this.CompileModuleName));
                ret = false;
            }

            return ret;
        }

        #region private helper

        /// <summary>
        /// compile one file
        /// </summary>
        /// <param name="msAccessFile">access file for compiling </param>
        /// <returns>returns true when success</returns>
        private bool CompileAndSaveMsAccessList(MsAccessFile msAccessFile)
        {
            return msAccessFile.CompileAndSave(this.references);
        }

        /// <summary>
        /// quit and dispose all MsAccessFile
        /// </summary>
        /// <param name="accfiles">access files for quit</param>
        /// <returns>returns true when success</returns>
        private static bool QuitMsAccessList(ICollection<MsAccessFile> accfiles)
        {
            bool ret = true;
            foreach (MsAccessFile file in accfiles)
            {
                if (!file.Quit()) ret = false;
                file.Dispose();
            }

            accfiles.Clear();
            return ret;
        }

        /// <summary>
        /// Import files
        /// </summary>
        /// <param name="accessFile">access file to import forms,reports etc</param>        
        /// <returns>returns true when success</returns>
        private bool FileImporter(MsAccessFile accessFile)
        {
            bool ret = true;            
            this.logger.LogInternal(string.Format("FileImporter importing"), false);
            int max = this.files.Count;
            do
            {
                //lock (this.files)
                //{
                string item = this.files.Dequeue();
                //}

                //this.logger.LogInternal(string.Format("Init accessobject start:{0}", item), false);
                var accessObject = new AccessObject(item);
                //this.logger.LogInternal(string.Format("Init accessobject:{0} end", item), false);
                if (!accessObject.HasError)
                {
                    // warning
                    if (accessObject.HasWarning)
                    {
                        this.logger.LogInternal(accessObject.ErrorMessage, false);
                    }
                    
                    if (!this.excludedList.Exists(s => accessObject.FileExt.ToLower() == s.ToLower()))
                    {
                        DateTime dt = DateTime.Now;
                        this.logger.LogInternal(string.Format("{1}.Import file:{0}", accessObject.ObjectName,max - this.files.Count), false);
                        ret = accessFile.ImportOneFile(accessObject);
                        this.logger.LogInternal(string.Format("{1}.Import file:{0} Elapsed time:{2}", accessObject.ObjectName, max - this.files.Count,DateTime.Now.Subtract(dt)), false);
                        // that case we do not import 
                        if (!ret)
                        {
                            this.logger.LogInternal(accessObject.ErrorMessage, true);
                            break;
                        }
                    }
                }
                else
                {
                    // break the import
                    this.logger.LogInternal(
                        string.Format("Error import file:{0} error:{1}", item, accessObject.ErrorMessage), true);
                    ret = false;
                    break;
                }
            }
            while (this.files.Count > 0);

            return ret;
        }

        /// <summary>
        /// initializes internal fields
        /// </summary>
        private void FieldBuilder()
        {
            string errorMessage;
            this.excludedList = Factory.GetExcludedList(this.ExcludedFileTypes);
            this.files = Factory.GetFiles(this.ImportPath, "*.a*");
            this.references = Factory.CreateAccessReferencesFromFile(this.ReferencesFileName, out errorMessage);
            if (!string.IsNullOrEmpty(errorMessage))
            {
                throw new Exception(errorMessage);
            }
        }
        
        /// <summary>
        /// close files
        /// </summary>
        /// <param name="accfiles">access file list for closing</param>
        /// <returns>returns true when success</returns>
        private static bool CloseMsAccessList(IEnumerable<MsAccessFile> accfiles)
        {
            bool ret = true;
            foreach (MsAccessFile file in accfiles)
            {
                ret = ret & file.Close();                
            }
            return ret;
        }        

        #endregion
    }
}